home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d18 / kruse_11.arc / INDEXTEX.PAS < prev    next >
Pascal/Delphi Source File  |  1990-11-30  |  37KB  |  987 lines

  1. {outline of declaration of subprograms:
  2.  
  3.  1.     program IndexText(InText, InIndex, NewIndex, OutIndex, HashFile,
  4.                           NewHashFile, input, output);     (main program)
  5.  2.         function Lt(u, v: word):  Boolean;
  6.  3.         procedure ReadWord(var f: text;  var w: word);
  7.  4.         procedure WriteWord(var f: text; w: word);
  8.  4a.        built in CPU time function   clock;
  9.  
  10.  5.         procedure SplitWords;                       (phase 1)
  11.  5a.            function FindFile(ch: char): filecode;
  12.  6.             function HashAddress(w: word):  hashentry;
  13.  7.             procedure Initialize;
  14.  8.             procedure GetWord;
  15.  8a.                procedure TellUserPage;
  16.  9.                 procedure GetChar(var ch: char);
  17. 10.                 procedure AddChar(ch: char);
  18. 11.             procedure Conclude;
  19.  
  20. 12.         procedure ClassifyWords;                    (phase 2)
  21. 13.             procedure BuildTree(var root: pointer; ch: char);
  22. 15.                 function Power2(c: integer): level;
  23.                   (the next three procedures are written in line.)
  24. 14.                 procedure Insert(p: pointer);
  25. 16.                 procedure FindRoot;
  26. 17.                 procedure ConnectSubtrees;
  27. 18.                 procedure GetNode(var p: pointer; ch: char);
  28. 19.             procedure Process(r: reference);
  29. 20.                 procedure UpdateNode(p: pointer; r: reference);
  30. 21.                 procedure NewWord(var p: pointer; r: reference);
  31. 22.                 procedure InsertTree(r, p: pointer);
  32. 23.             procedure OutputTree(p: pointer);
  33. 24.                 procedure PutNode(p: pointer);
  34. }
  35.  
  36.  
  37. program IndexText(InText, InIndex, NewIndex, HashFile, NewHashFile,
  38.                   input, output);
  39.  
  40. {Produces word counts and list of references for the document file 
  41.  InText. Uses the master word list in file InIndex, if provided. Output word
  42.  list for new text goes to file NewIndex. HashFile contains the common words
  43.  to be ignored. If not specified, it is created on output, containing the
  44.  words so flagged by the user.}
  45. {This implementation uses only phases 1 and 2. A smaller array of text files
  46.  is also used, as specified in the exercise section.}
  47.  
  48. const
  49.   maxwd         =   20;             {More letters in word will be ignored.}
  50.   minwd         =    1;                    {Shorter words will be ignored.}
  51.   hashsize      = 2003;                                 {should be a prime}
  52.   linesperpage  =  100;          {set to a value not to interfere with TeX}
  53.   maxheight     =   20;               {for building binary tree in phase 2}
  54.   A             =  'A';
  55.   Z             =  'Z';
  56.   hyphen        =  '-';
  57.   blank         =  ' ';
  58.   apostrophe    = '''';               {requires two `'s  to represent one}
  59.   underscore    =  '_';
  60.   ordbackspace  =    8;            {ASCII control character for backspace}
  61.   ordformfeed   =   12;             {ASCII control character for new page}
  62.   changecase    =   32;    {ASCII difference between upper and lower case}
  63.   nfiles        =    8;  {number of temporary files for unprocessed words}
  64.   MaxRowLength  =   130;                 {maximum length of output records}
  65.  
  66. type
  67.   word          =  packed array[1..maxwd] of char;
  68.   reference     =  record
  69.                       wd:   word;
  70.                       pg:   integer;               {count or page number}
  71.                    end;
  72.   fileref       =  file of reference;              {used for local files}
  73.   letter        =  A..Z;
  74.   hashentry     =  1..hashsize;
  75.   filecode      =  1..nfiles;
  76.  
  77. var
  78.   InText,                                     {document being processed}
  79.   InIndex,                                            {master word list}
  80.   NewIndex,                              {word list of current document}
  81.   HashFile,
  82.   NewHashFile:      text;
  83.   RefFile:      array[filecode] of fileref; {local files used for auxilary
  84.                                storage of words from phase 1 to phase 2:
  85.                 Normally, a separate file exist for each initial letter,
  86.         this version uses nfiles files due operating system constraints.}
  87.   blankword:    word;                           {will contain all blanks}
  88.  
  89. {The next two variables were originally declared in procedure SplitWords,
  90.  they have been moved to this level in order to access them globally.}
  91.   outcount:     array[filecode] of integer;    {counters for word  files}
  92.   wordcount:    integer;                 {count of all words in the text}
  93.  
  94.   intextname,
  95.   inlistname,
  96.   newlistname,
  97.   newhashname:  word;                    {used to get filename from user}
  98.   lastletter:   array[filecode] of letter;     {last letter in each file}
  99.   PresentTime,
  100.   StartTime:    integer;                         {used to track CPU time}
  101.   RowLength:    integer;   {ensures records will not exceed MaxRowLength}
  102.  
  103.  
  104.  
  105. function Lt( u, v: word): Boolean;
  106. {Determains if word u precedes word v lexicographically.}
  107. begin
  108.   Lt := (u < v)
  109. end;
  110.  
  111. procedure ReadWord( var F: text;  var w: word);
  112. {Reads word w from text file F.  Assumes not at end of file.}
  113. {Uses packed array, replace using a loop if your system does not 
  114.  support packed arrays. }
  115. begin                           {procedure ReadWord}
  116.   read(F, w)
  117. end;                            {procedure ReadWord}
  118.  
  119. procedure WriteWord( var F: text; w: word);
  120. {Writes word w to text file F}
  121. {Uses packed array, replace using a loop if your system does not 
  122.  support packed arrays. }
  123. begin                           {procedure WriteWord}
  124.   write(F, w)
  125. end;                            {procedure WriteWord}
  126.  
  127. procedure SetTimer;     {Call once at beginning of program execution.}
  128. {Finds the CPU time when called, and keeps in variables for reference.}
  129. {System dependent procedure.}
  130. begin
  131.   PresentTime := clock;
  132.   StartTime := PresentTime;
  133. end;
  134.  
  135. function TotalTime:  real;
  136. {Returns the total CPU time, in seconds, since call to SetTimer.}
  137. {System dependent procedure.}
  138. begin
  139.   TotalTime := (clock - StartTime) / 1000.0;
  140. end;
  141.  
  142. function ElapsedTime:  real;
  143. {Returns elapsed CPU time since last call to function ElapsedTime,
  144.  or call to SetTimer, whichever is more recent.}
  145. {System dependent procedure.}
  146. var r: integer;
  147. begin
  148.   r := clock;
  149.   ElapsedTime := (r - PresentTime) / 1000.0;
  150.   PresentTime := r;
  151. end;
  152.  
  153.  
  154.  
  155. procedure SplitWords;
  156. {sets up hash table, reads text, and divides into nfiles word lists}
  157.  
  158. var
  159.   hash:       array[hashentry] of reference;              {hash table}
  160.   pagecount:  integer;                 {keeps the current page number}
  161.   addpage:    integer;       {amount to increase pagecount after word}
  162.   linecount:  integer;                     {lines on the current page}
  163.   w:          word;                   {word currently being processed}
  164.   x:          hashentry;             {location of w, if in hash table}
  165.   endinput:   Boolean;   {true if and only if input has all been read}
  166.   code:       filecode;                {into which file does word go?}
  167.  
  168. {The following variables are kept for use in procedure GetWord, and for
  169.  efficiency are set up only once in procedure Initialize:}
  170.   backspace,
  171.   formfeed:   char;
  172.   alphabet,                           {letters only - to start a word}
  173.   contchar:   set of char;     {other characters ok in middle of word}
  174.  
  175.  
  176.   function  FindFile( ch:  letter):  filecode;
  177.   {Uses binary decision tree to select one of nfiles = 8 files depending
  178.    on the letter ch.  These letters must be the same as those in the
  179.    global array  lastletter  .}
  180.  
  181.   begin                           {function FindFile}
  182.     if            ch < 'M' then
  183.       if          ch < 'E' then
  184.         if        ch < 'C' then  FindFile := 1
  185.                            else  FindFile := 2
  186.       else if     ch < 'H' then  FindFile := 3
  187.                            else  FindFile := 4
  188.     else if       ch < 'S' then
  189.       if          ch < 'P' then  FindFile := 5
  190.                            else  FindFile := 6
  191.       else if     ch < 'T' then  FindFile := 7
  192.                            else  FindFile := 8
  193.   end;                            {function FindFile}
  194.  
  195.  
  196.  
  197.   function HashAddress(w: word): hashentry;
  198.   {calculates the location in hash table of word w, or, if not there,
  199.    returns pointing to the blank word where w should go}
  200.  
  201.   var
  202.     x,                            {calculated location}
  203.     inc:     integer;             {increment for open addressing}
  204.   begin                           {function HashAddress}
  205.     x := abs(ord(w[1])*ord(w[2])+ord(w[4])+ord(w[6])) mod hashsize + 1;
  206. {Hash function assumes long word length. For short word machines
  207.  we must ensure that the result is non-negative, and worry about overflow.}
  208.  
  209.     if (hash[x].wd <> w) and (hash[x].wd <> blankword) then
  210.       begin
  211.         inc   := (abs(ord(w[3])-95) mod 29);
  212.                   {A key dependent increment is used to avoid clustering.}
  213.         repeat
  214.           inc := inc + 1;
  215.           if inc > hashsize then
  216.             writeln(w,' causes hash table to become full, infinite loop.');
  217.           x := x + inc;
  218.           if x > hashsize then x := x - hashsize;
  219.         until (w =  hash[x].wd)  or  (blankword = hash[x].wd)
  220.       end;
  221.     HashAddress := x
  222.   end;                            {function HashAddress}
  223.  
  224.  
  225.   procedure Initialize;
  226.   {sets up constant-valued sets for use in GetWord. Opens the text file
  227.    and initializes various counters. Opens file holding hash table (if any),
  228.    and reads or otherwise initializes table}
  229.   var
  230.     i:         integer;          {general purpose loop control}
  231.  
  232.   begin                           {procedure Initialize}
  233.     backspace:= chr(ordbackspace);
  234.     formfeed := chr(ordformfeed); {initialize ASCII control characters}
  235.     alphabet := ['A'..'Z', 'a'..'z'];      {letters only, to start a word}
  236.     contchar := [hyphen, apostrophe];      {, backspace, underscore];}
  237.                                 {characters which will not terminate word}
  238.     for i := 1 to maxwd do
  239.       blankword[i] := blank;
  240.  
  241.     write('Name of input text file?');
  242.     ReadWord(input, intextname); readln;
  243.     open(InText, intextname, readonly);
  244.     reset(InText);
  245.     endinput := eof(InText);
  246.  
  247.     repeat
  248.       write( 'What is the page number on which the text begins?');
  249.       readln(pagecount);
  250.       if pagecount < 0 then
  251.         writeln('Must be a non-negative integer.')
  252.     until pagecount >= 0;
  253.     linecount := 0;
  254.     addpage   := 0;
  255.     wordcount := 0;
  256.  
  257.     for i := 1 to nfiles do
  258.     begin
  259.       rewrite( RefFile[i] );
  260.       outcount[i] := 0
  261.     end;
  262.     lastletter[1] := 'B';
  263.     lastletter[2] := 'D';
  264.     lastletter[3] := 'G';
  265.     lastletter[4] := 'L';
  266.     lastletter[5] := 'O';
  267.     lastletter[6] := 'R';
  268.     lastletter[7] := 'S';
  269.     lastletter[8] := 'Z';
  270.  
  271.     reset(HashFile);   {assumes HASHFILE.DAT is in current directory}
  272.  
  273.     for i := 1 to hashsize do
  274.     with hash[i] do 
  275.       begin
  276.         read(HashFile, pg);
  277.         get(HashFile);         {skip the blank between number and word}
  278.         ReadWord(HashFile, wd);
  279.         readln(HashFile);
  280.         pg := 0;                     {initialize all the counts to 0}
  281.       end;
  282.     writeln('The hash table has been read.')
  283.   end;                                        {procedure Initialize}
  284.  
  285.  
  286.  
  287.   procedure GetWord( var  w: word);
  288.   {Gets words from input file InText, and returns only words
  289.    at least minwd characters long.  Parameter endinput becomes
  290.    true if and only if the end of InText is reached with no word to return.
  291.    the procedure also updates global variables wordcount and linecount,
  292.    updates the global variable pagecount after each linesperpage cr's,
  293.    or after each formfeed, whichever comes first, and
  294.    uses the sets alphabet and contchar and various character constants.}
  295.  
  296.   label 1;           {used by GetChar to exit procedure upon eof(InText)}
  297.  
  298.   var  c:      0..maxwd;                    {count of characters in word}
  299.        ch:     char;                      {character currently processed}
  300.        endln:  Boolean;                           {at the end of a line?}
  301.  
  302.  
  303.   procedure TellUserPage;         {keep the user informed of progress}
  304.   var   i: integer;
  305.   begin
  306.     i := pagecount + addpage;
  307.     writeln('At page', i:4, ' word count is', wordcount:7)
  308.   end;
  309.  
  310.   procedure TeXCommand(var ch: char);
  311.   var
  312.     i:  integer;         {used to construct word 'page' in TeX command}
  313.     wd: packed array[1..4] of char;  {holds word possibly = 'page'}
  314.   begin
  315.     ch := InText^;    {This character will be deleted.}
  316.     get(InText);      {Keep InText buffered one character ahead.}
  317.     if ch in alphabet then
  318.     begin                 {case: a word follows '\'}
  319.       i := 0;
  320.       while Intext^ in alphabet do {Delete all following letters}
  321.       begin
  322.         i := i + 1;
  323.         if i <= 4 then wd[i] := InText^;
  324.         get(InText)
  325.       end;
  326.       if (i = 4) and (wd = 'page') then
  327.         ch := formfeed
  328.       else begin            {case: word after '\' not 'page'}
  329.         ch := InText^;
  330.         get(InText)
  331.       end                   {case: word after '\' not 'page'}
  332.     end                     {case: a word follows '\'}
  333.     else begin           {case: character after '\' not a letter}
  334.       ch := Intext^;
  335.       get(InText)   {Delete only one character more if it is not a letter.}
  336.     end             {case: character after '\' not a letter}
  337.   end;
  338.  
  339.   procedure GetChar(var ch: char);
  340.   {gets a character from input text into ch; checks for eof; updates
  341.    page count and line count; deletes all TeX commands}
  342.   begin                                                {procedure GetChar}
  343.     if eof(InText) then
  344.       if c >= minwd then
  345.         ch := '.'              {special character to end the current word}
  346.       else begin                         {no word to return; set endinput}
  347.         endinput := true;
  348.         goto 1                                        {exit from GetWord.}
  349.       end                                {no word to return}
  350.     else begin                   {not end of file: process next character}
  351.       while InText^ = backspace do  {delete use of underscore; TeX subscript}
  352.         get( InText);
  353.       ch := InText^;
  354.       endln := eoln(InText);
  355.       get(InText);
  356.       while ch = '\' do
  357.         TeXCommand(ch);
  358.       if endln then
  359.       begin                             {case:  end of line}
  360.         linecount := linecount + 1;
  361.         if linecount >= linesperpage then
  362.         begin
  363.           addpage := addpage + 1;
  364.           linecount := 0;
  365.           TellUserPage
  366.         end
  367.       end;                               {case:  end of line}
  368.       if ch = formfeed then
  369.       begin                              {case:  formfeed}
  370.         addpage := addpage + 1;
  371.         linecount := 0;
  372.         TellUserPage;
  373.         endln := true;            {Treat formfeed like end of line.}
  374.         ch := blank
  375.       end                                 {case:  formfeed}
  376.     end                                   {case:  not at end of file}
  377.   end;                                            {procedure GetChar}
  378.  
  379.  
  380.   procedure AddChar(ch: char);
  381.   {adds given character to word, if possible}
  382.   begin                           {procedure AddChar}
  383.     if c < maxwd then
  384.     begin
  385.       c := c + 1;
  386.       w[c] := ch
  387.     end
  388.   end;                            {procedure AddChar}
  389.  
  390.  
  391.   begin                           {procedure GetWord}
  392.     repeat                {until current word is at least minwd chars long}
  393.       c := 0;
  394.       repeat
  395.         GetChar(ch)               {Find a letter which will start the word.}
  396.       until ch in alphabet;
  397.       pagecount := pagecount + addpage;
  398.       addpage := 0;
  399.       if ch in ['a'..'z'] then       {translate first letter to upper case.}
  400.         ch := chr(ord(ch) - changecase); {assumes ASCII ordering of letters}
  401.       AddChar(ch);                          {put first letter into the word}
  402.       GetChar(ch);
  403.       while (ch in alphabet) or (ch in contchar) do
  404.         if ch in alphabet then                {add letters directly to word}
  405.         begin                                            {processing letter}
  406.           AddChar(ch);
  407.           GetChar(ch)
  408.         end                                              {processing letter}
  409.         else if ch = hyphen then
  410.         begin                                            {processing hyphen}
  411.           GetChar(ch);                       {Find what comes after hyphen.}
  412.           if endln then
  413.             while ch = ' ' do
  414.               GetChar(ch)       {Delete both the hyphen and the end of line}
  415.           else if ch = hyphen then      {Two hyphens form a dash; ends word}
  416.             ch := blank                 {Use a blank to terminate the word.}
  417.           else if ch in alphabet then
  418.             AddChar(hyphen)                  {Include other hyphens in word}
  419.           else      {nothing}
  420.         end                                              {processing hyphen}
  421.         else if ch = apostrophe then
  422.         begin                                        {processing apostrophe}
  423.           GetChar(ch);
  424.           if ch = 's' then              {Delete  `'s'   at end of word only}
  425.           begin
  426.             GetChar(ch);
  427.             if ch in contchar then
  428.             begin
  429.               AddChar(apostrophe);
  430.               AddChar('s')
  431.             end
  432.           end
  433.           else if ch in alphabet then
  434.              AddChar(apostrophe)                      {Allow contractions.}
  435.         end                                         {processing apostrophe}
  436.         else         {Remaining possibilities are backspace and underscore.}
  437.           GetChar(ch);                           {Delete these characters.}
  438.       {While loop on continuing characters ends here.}
  439.       wordcount := wordcount + 1
  440.     until c >= minwd;                              {Skip over short words.}
  441.  
  442.     while c < maxwd do                                  {Fill with blanks.}
  443.     begin
  444.       c := c + 1;
  445.       w[c] := blank
  446.     end;
  447.   1:      {When end of file occurs, program will exit to here from GetChar}
  448.   end;                                                  {procedure GetWord}
  449.  
  450.  
  451.  
  452. procedure Conclude;
  453. {Writes out counts of various word lists. For some systems, it is 
  454.  necessary to close files, which should be done here.}
  455.  
  456. var
  457.   i,j:        integer;                                {loop index}
  458.   response:   char;                    {user's answer to question}
  459.  
  460. begin                           {procedure Conclude}
  461.   writeln('The total number of words read in is ', wordcount:7);
  462.   writeln;
  463.   writeln('The number of words to process further in the next stage,');
  464.   writeln('in each temporary file, is below.');
  465.   writeln('     a-b     c-d     e-g     h-l     m-o     p-r      s      t-z');
  466.   for i := 1 to nfiles do
  467.     write(outcount[i]:8);
  468.   writeln;
  469.   writeln;
  470.  
  471.   repeat
  472.     write('Do you wish the counts from hash table to be kept in a file (y,n)?');
  473.     readln(response);
  474.     if response > 'Z' then response := chr(ord(response)-changecase)
  475.   until response in ['N', 'Y'];
  476.   if response = 'Y' then
  477.   begin
  478.     write('Name of file ?');
  479.     ReadWord(input, newhashname);
  480.     readln;
  481.     open(NewHashFile, newhashname, new);
  482.     rewrite(NewHashFile);
  483.  
  484.     for i := 1 to hashsize do
  485.     with hash[i] do begin
  486.       write(NewHashFile, pg:4, ' ');
  487.       j := 1;
  488.       repeat
  489.         write(NewHashFile, wd[j]);
  490.         j := j + 1;
  491.       until (wd[j] = ' ') or (j >= maxwd);
  492.       writeln(NewHashFile)
  493.     end
  494.   end
  495. end;                            {procedure Conclude}
  496.  
  497.  
  498. begin                                          {procedure  SplitWords}
  499.   Initialize;                   {sets up files, hash table, constants}
  500.   GetWord(w);                       {obtain a single word from InText}
  501.   while not endinput do
  502.   begin
  503.     x := HashAddress(w);
  504.     if w = hash[x].wd then
  505.       hash[x].pg := hash[x].pg + 1
  506.     else begin                  {not in hash table; put into RefFile}
  507.       code := FindFile( w[1] );
  508.       outcount[code] := outcount[code] + 1;
  509.       with RefFile[code]^ do
  510.       begin
  511.         wd := w;
  512.         pg := pagecount
  513.       end;
  514.       Put(RefFile[code])
  515.     end;
  516.     GetWord(w);
  517.   end;
  518.   Conclude                           {writes word counts to output.}
  519. end;                                          {procedure SplitWords}
  520.  
  521.  
  522.  
  523. {start of phase 2}
  524.  
  525. procedure ClassifyWords;
  526. {For each letter of the alphabet, the procedure reads in a list of
  527.  words from InIndex, builds them into a binary tree, supplements it
  528.  with entries from RefFile, and writes the result to files NewIndex
  529.  and NewHashFile.}
  530.  
  531. type
  532.   wordtype  = (hash, count, page, question, index); {ways to process a word}
  533.   pointref  = ^reflist;
  534.   reflist   = record                            {list of references}
  535.                 pg:   integer;
  536.                 next: pointref
  537.               end;
  538.   pointer   = ^node;
  539.   node      = record                    {vertex of the binary tree}
  540.                 wd:       word;
  541.                 left,
  542.                 right:    pointer;
  543.                 ct:       integer;
  544.                 case kind:  wordtype of
  545.                   hash, count:
  546.                     ();
  547.                   page, question, index:
  548.                     (ref:   pointref)
  549.               end;
  550. var
  551.   root:       pointer;                    {root of binary tree}
  552.   code:       filecode;          {loop through temporary files}
  553.   endlist:    Boolean;             {at end of input word list?}
  554.   i:          integer;          {general purpose loop variable}
  555.  
  556.  
  557.  
  558. procedure BuildTree(var root: pointer;  code: filecode);
  559.  
  560. {Reads a sequential file in alphabetical order, and converts it into
  561.  a binary search tree. Stops reading when the first letter of word
  562.  is after lastletter[code].
  563.  const  maxheight = 20  (in main program) allows 512k entries.}
  564.  
  565. {This procedure was modified slightly to fit the needs of this application.
  566.  The parameters of GetNode now include a character ch, which has also
  567.  been introduced as a local variable.}
  568.  
  569. type
  570.   level = -1 .. maxheight;      {number of steps above leaves}
  571.  
  572. var
  573.   lastnode:  array[level] of pointer;   {contains pointer to
  574.                          last node processed on each level}
  575.   counter:   integer;           {number of nodes read in so far}
  576.   p:         pointer;           {p^ is present input node}
  577.   lev:       level;             {level of p^}
  578.   ch:        char;              {will be last letter to be processed.}
  579.  
  580.  
  581.   function Power2(c:  integer): level;
  582.   {finds the highest power of 2 which divides c}
  583.   var
  584.     lev:   level;
  585.   begin                           {function Power2}
  586.     lev := 0;
  587.     while not odd(c) do
  588.     begin
  589.       c := c div 2;
  590.       lev := lev + 1
  591.     end;
  592.     Power2 := lev
  593.   end;                            {function Power2}
  594.  
  595.  
  596.   procedure Insert(p: pointer);
  597.   {Inserts p^ as rightmost node of a partial binary search tree.}
  598.   var
  599.     lev:       level;      {level of p^}
  600.   begin                    {Procedure Insert}
  601.     lev      := Power2(counter);
  602.     p^.right := nil;
  603.     p^.left  := lastnode[lev - 1];
  604.     lastnode[lev] := p;
  605.     if lastnode[lev + 1] <> nil then
  606.       with lastnode[lev + 1]^ do
  607.       if right = nil then right := p
  608.   end;                     {Procedure Insert}
  609.  
  610.  
  611.   procedure FindRoot;
  612.   var
  613.     lev:    level;
  614.   begin                    {Procedure FindRoot}
  615.     if counter = 0 then
  616.       root := nil          {Tree is empty.}
  617.     else begin             {Non-empty tree}
  618.       lev := maxheight;    {Find the highest occupied level; it gives the root}
  619.       while lastnode[lev] = nil do lev := lev - 1;
  620.       root := lastnode[lev]
  621.     end
  622.   end;                     {Procedure FindRoot}
  623.  
  624.  
  625.   procedure ConnectSubtrees;
  626.   var
  627.     p:         pointer;
  628.     lev:       level;
  629.     s:         level;
  630.   begin                    {Procedure ConnectSubtrees}
  631.     lev := maxheight;
  632.     while (lastnode[lev] = nil) and (lev > 1) do
  633.       lev := lev - 1;      {Find the highest node:  root}
  634.     while lev > 1 do       {Nodes on levels 1 and 0 are already OK}
  635.       with lastnode[lev]^ do
  636.       if right <> nil then
  637.         lev := lev - 1     {Search down for the highest dangling node}
  638.       else begin           {Case:  right subtree is undefined.}
  639.         p := left;         {Find the highest entry in lastnode that}
  640.         s := lev - 1;                     {is not in the left subtree.}
  641.         repeat
  642.           p := p^.right;
  643.           s := s - 1
  644.         until (p = nil) or (p <> lastnode[s]);
  645.         right := lastnode[s];
  646.         lev := s           {Nodes on levels between lev and s are on the left.}
  647.       end                  {Connecting dangling subtrees}
  648.   end;                     {Procedure  ConnectSubtrees}
  649.  
  650.  
  651.   procedure GetNode( var p: pointer;  ch:  char);
  652.   {reads a word from file  InIndex  and sets node correspondingly}
  653.   {returns p = nil at eof or when next word starts later than code.}
  654.   var
  655.     wordcode:  char;                {letter indicating type of word}
  656.  
  657.   begin                                          {procedure GetNode}
  658.     while InIndex^ = '&' do         {ignore lines starting with '&'}
  659.       readln(InIndex);
  660.     while (not eof(InIndex)) and (InIndex^ = blank) do
  661.       get(InIndex);                        {Skip all leading blanks}
  662.     if endlist or eof(InIndex) then
  663.       p := nil
  664.     else if InIndex^ > ch then
  665.       p := nil
  666.    else begin
  667.       new(p);
  668.       with p^ do begin
  669.         ReadWord(InIndex, wd);
  670.         while (InIndex^ = ' ') and (not eoln(InIndex)) do
  671.           get(InIndex);
  672.         read(InIndex, wordcode);
  673.         ct := 0;
  674.         if wordcode in ['C', 'H','I','P','?'] then
  675.         case wordcode of
  676.           'C':  kind := count;
  677.  
  678.           'H':  begin
  679.                   writeln('Warning: The input word list contains ', wd);
  680.                   writeln('         which belongs in the hash table.');
  681.                   kind := hash
  682.                 end;
  683.  
  684.           'I':  begin kind := index;     ref := nil  end;
  685.           'P':  begin kind := page;      ref := nil  end;
  686.           '?':  begin
  687.                   writeln('Questionable word: ', wd, ' in word list.');
  688.                   write('New category (P, I, C, H, ?');
  689.                   repeat
  690.                     readln(wordcode);
  691.                     if wordcode > 'Z' then 
  692.                       wordcode := chr(ord(wordcode) - changecase)
  693.                   until wordcode in ['H','C','P','?','I'];
  694.                   case wordcode of
  695.                     'H':       kind := hash;
  696.                     'C':       kind := count;
  697.                     'P', ' ':  kind := page;
  698.                     '?':       kind := question;
  699.                     'I':       kind := index
  700.                   end;
  701.                   if kind in [page, question, index] then ref := nil
  702.                 end
  703.           end
  704.           else
  705.             writeln('Erroneous word code ', wordcode, ' in file InIndex.')
  706.       end;                           {with statement setting up the node}
  707.       readln(InIndex);          {Advance to the start of the next entry.}
  708.       endlist := eof(InIndex)
  709.     end
  710.   end;                                                {procedure GetNode}
  711.  
  712.  
  713. begin                           {procedure BuildTree}
  714.   for lev := -1 to maxheight do  lastnode[lev] := nil;
  715.   counter := 0;
  716.   ch := lastletter[code];
  717.   GetNode(p, ch);
  718.   while p <> nil do
  719.   begin
  720.     counter  := counter + 1;
  721.     Insert(p);
  722.     GetNode(p, ch)
  723.   end;                          {reading and processing input}
  724.   FindRoot;
  725.   ConnectSubtrees
  726. end;                            {procedure  BuildTree}
  727.  
  728.  
  729.  
  730. procedure Process( r: reference);
  731. {Takes the word and page reference r, and updates the binary tree.}
  732. var
  733.   p:          pointer;                      {trace through the tree}
  734.   found:      Boolean;                    {Is the word in the tree?}
  735.  
  736.  
  737. procedure UpdateNode( p:  pointer;  r: reference);
  738. {uses reference r to update information in node p^}
  739.  
  740. var
  741.   q:     pointref;              {used to add reference to list}
  742. begin                                    {procedure UpdateNode}
  743.   with p^ do
  744.   begin
  745.     ct := ct + 1;
  746.     if  kind  in  [page, question, index] then
  747.       if ref = nil then
  748.       begin
  749.         new(ref);
  750.         ref^.pg   := r.pg;
  751.         ref^.next := nil
  752.       end
  753.       else if ref^.pg <> r.pg then
  754.       begin                     {add the new reference to list.}
  755.         new(q);
  756.         q^.pg   := r.pg;
  757.         q^.next := ref;
  758.         ref     := q
  759.       end
  760.   end                           {with statement to update tree}
  761. end;                            {procedure UpdateNode}
  762.  
  763.  
  764. procedure NewWord(var p: pointer;  r: reference);
  765. {Creates a node for the first occurrence of a new reference r. A
  766.  pointer to the new node is returned in p.}
  767.  
  768. var
  769.   response:       char;                {answer received from user}
  770. begin                                          {procedure NewWord}
  771.   new(p);
  772.   with p^ do
  773.   begin
  774.     wd    := r.wd;
  775.     left  := nil;
  776.     right := nil;
  777.     ct    := 1;
  778.  
  779.     kind  := question;
  780.     repeat                           {ask user what kind of word}
  781.       WriteWord(output, wd);
  782.       write('  is (H, C, P, ?, I)?');
  783.       readln(response);
  784.       if response > 'Z' then response := chr(ord(response) - changecase)
  785.     until response in ['H', 'C', 'P', ' ', '?', 'I'];
  786.     case response of
  787.       'H':         kind := hash;
  788.       'C':         kind := count;
  789.       'P', ' ':    kind := page;
  790.       '?':         begin
  791.                      kind := question;
  792.                      writeln('First occurence of word is on page', r.pg:5, '.')
  793.                    end;
  794.       'I':         kind := index
  795.     end;  {case statement}
  796.     if kind in [page, question, index] then
  797.     begin
  798.       new(ref);
  799.       ref^.pg   := r.pg;
  800.       ref^.next := nil;
  801.     end
  802.   end                                          {with statement}
  803. end;                                        {procedure NewWord}
  804.  
  805.  
  806. procedure InsertTree(r, p: pointer);
  807. {adds a node p^ to the tree with root r^; requires that r <> nil
  808.  and p^ not be in the tree; proceeds by recursion}
  809.  
  810. begin                           {procedure InsertTree}
  811.   if Lt(p^.wd, r^.wd) then
  812.     if r^.left = nil then r^.left := p
  813.     else InsertTree(r^.left, p)
  814.   else
  815.     if r^.right = nil then r^.right := p
  816.     else InsertTree(r^.right, p)
  817. end;                            {procedure InsertTree}
  818.  
  819.  
  820. begin                                        {procedure Process}
  821.   if root = nil then                  {The tree might be empty.}
  822.     NewWord(root, r)
  823.   else begin                            {case of non-empty tree}
  824.     p := root;                            {Begin a tree search.}
  825.     found := false;
  826.     repeat
  827.       if r.wd = p^.wd then
  828.         found := true
  829.       else if Lt(r.wd,p^.wd) then
  830.         p := p^.left
  831.       else
  832.         p := p^.right
  833.     until found or (p = nil);
  834.  
  835.     if found then UpdateNode(p, r)
  836.     else begin                  {p^ was not found: add it to the tree.}
  837.       NewWord(p, r);
  838.       InsertTree(root, p)
  839.     end
  840.   end
  841. end;                                               {procedure Process}
  842.  
  843.  
  844. procedure OutputTree( p: pointer);
  845. {traverses the tree for which p^ is the root in inorder}
  846.  
  847. procedure PutNode( p:  pointer);
  848. {Puts the information in p^ into the file NewIndex.}
  849.  
  850. var
  851.   q:        pointref;           {used to traverse list of references}
  852.   response: char;
  853. begin                                             {procedure PutNode}
  854.   with p^ do  if ct > 0 then
  855.   begin                         {Otherwise, word is not in document.}
  856.     if kind <> hash then
  857.       WriteWord(NewIndex, wd);
  858.     case kind of
  859.       hash:      begin      {new hash entries written to NewHashFile}
  860.                    write(NewHashFile, ct, ' ');
  861.                    WriteWord(NewHashFile, wd);
  862.                    writeln(NewHashFile)
  863.                  end;
  864.       count:     write(NewIndex, 'C');
  865.       page:      write(NewIndex, 'P');
  866.       index:     write(NewIndex, 'I');
  867.       question:
  868.         begin
  869.           repeat                      {ask user what kind of word}
  870.             WriteWord(output, wd);
  871.             write('  is questionable.  Change to (h, c, p, ?, i)?');
  872.             readln(response);
  873.             if response > 'Z' then response := chr(ord(response) - changecase)
  874.           until response in ['H', 'C', 'P',' ', '?', 'I'];
  875.           case response of
  876.             'H':       begin kind := hash;   write(NewIndex, 'H') end;
  877.             'C':       begin kind := count;  write(NewIndex, 'C') end;
  878.             'P', ' ':  begin kind := page;   write(NewIndex, 'P') end;
  879.             'I':       begin kind := index;  write(NewIndex, 'I') end;
  880.             '?':       begin
  881.                          kind := question;
  882.                          write(NewIndex, '?');
  883.                          write('The word appears on the following page(s)');
  884.                          q := ref;
  885.                          repeat
  886.                            write(q^.pg:6);
  887.                            q := q^.next
  888.                          until q = nil;
  889.                          writeln
  890.                        end                  {case of questionable word}
  891.           end                                 {case response statement}
  892.         end                            {treating new or question words}
  893.     end;                                {case kind statement}
  894.     if kind <> hash then
  895.       write(NewIndex, ct:6);
  896.     if kind in [page, question, index] then
  897.     begin
  898.       q := ref;
  899.       RowLength := 28;   {ensures that record will not exceed desired length}
  900.       repeat
  901.         if RowLength > (MaxRowLength - 4) then
  902.           begin
  903.             writeln(NewIndex);
  904.             write(NewIndex,'&  ');     {& indicates continuation of index}
  905.             RowLength := 3
  906.           end;
  907.         write( NewIndex, q^.pg:4);
  908.         q := q^.next;
  909.         RowLength := RowLength + 4
  910.       until q = nil;
  911.     end;
  912.     if kind <> hash then
  913.       writeln( NewIndex )
  914.   end                           {with statement and if statement}
  915. end;                            {procedure PutNode}
  916.  
  917.  
  918. begin                                       {procedure OutputTree}
  919.   if p <> nil then
  920.   with p^ do
  921.   begin
  922.     OutputTree(left);                  {Traverse the left subtree}
  923.     PutNode(p);
  924.     OutputTree(right);                {Traverse the right subtree}
  925.     dispose(p)
  926.   end
  927. end;                                        {procedure OutputTree}
  928.  
  929.  
  930.  
  931. begin                           {procedure ClassifyWords}
  932.  
  933.   write('Name of input word list ?');
  934.   ReadWord(input, inlistname);
  935.   readln;
  936.   open(InIndex, inlistname, readonly);
  937.   reset(InIndex);
  938.   endlist := eof(InIndex);
  939.  
  940.   write('Name of output word list ?');
  941.   ReadWord(input, newlistname);
  942.   readln;
  943.   open(NewIndex, newlistname, new);
  944.   rewrite(NewIndex);
  945.  
  946.   writeln('At the appearance of each word, indicate its disposition:');
  947.   writeln('  H -  Place this word in hash table and count its frequency.');
  948.   writeln('  C -  Count how many times this word appears.');
  949.   writeln('  P -  List pages on which this word appears.');
  950.   writeln('  ? -  Question this word: list pages on which it appears.');
  951.   writeln('  I -  Index this word: list pages on which it appears.');
  952.  
  953.   for code := 1 to nfiles do      {start main loop through temporary files.}
  954.   begin
  955.     BuildTree(root, code);  {Get the part of master wordlist starting with
  956.                code from the file InIndex, and build it into a binary tree.}
  957.     reset(RefFile[code]);
  958.     for i := 1 to outcount[code] do
  959.     begin
  960.       Process(RefFile[code]^);
  961.                      {use new words from RefFile[code] to update the tree.}
  962.       get( RefFile[code] )
  963.     end;
  964.  
  965.     OutputTree(root)
  966.                    {write the contents of the tree into file NewIndex.}
  967.   end                                    {main loop on temporary files}
  968. end;                                          {procedure ClassifyWords}
  969.  
  970. {end of all procedures}
  971.  
  972.  
  973. begin                                                    {main program}
  974.   SetTimer;
  975.   SplitWords;                                                 {Phase 1}
  976.   writeln('Time in first phase is ', ElapsedTime:7:1, '   seconds.');
  977.   writeln;
  978.  
  979.   ClassifyWords;                                              {Phase 2}
  980.   writeln('Time in second phase is', ElapsedTime:7:1, '  seconds.');
  981.  
  982.   writeln;
  983.   writeln('Processing of input document ', intextname, '  is complete.');
  984.   writeln('Total time in program was ', TotalTime:7:1, '   seconds.')
  985. end.
  986.  
  987.